/*
 * Terrain.h
 *
 *  Created on: Oct 10, 2012
 *      Author: travis
 */

#pragma once

// shader types
// for first pass during creating shadow map
#define SHADER_SHADOW	0

// second pass draw object with it's own shader program
#define SHADER_NORMAL	1

// dont draw mesh under water surface
// enable plane clipping
#define SHADER_CLIP_UNDER	2

// draw terrain surface to texture
// enable plane clipping by water surface
#define SHADER_CLIP_ABOVE	3

#include "ShaderPool.h"

#ifdef _WIN32 
#include <windows.h> 
#include "SDL.h"
#include "SDL_image.h"
#include "SDL_thread.h"
#else
#include "SDL/SDL.h"
#include "SDL/SDL_image.h"
#include "SDL/SDL_thread.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "TexPool.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "special/VBOIndexer.h"
#include "scene/World.h"
#include "Water.h"

#include "dataTypes/DataTypes.h"

#include "dataTypes/Cube.h"
#include "engine/engineSettings.h"

#include "engine/RTSException.h"

// maximum size of terrain
#define MAXSIZE 1025

namespace Terrain
{

class Terrain
{
public:
	// constructor
	// input - directory with textures
	//		 - pointer to shader pool
	Terrain(Water *_water, int size, float step);
	~Terrain(void);
	float STEP;

	// set part of terrain in input cube to avg value
	// for bounding boxes
	void createBuildPlace(DataTypes::Cube *cubePlace, glm::vec2 pos);

	// set size of terrain and clear surface
	// input - int size of terrain
	void terrainSetup(int size, float step);

	// return dif of high and low point on terrain
	float getDif();

	// return size of terrain
	int get_size(void);

	// return y value of vertex on x and y
	// in 2D array
	// input - int _X value
	//		 - int _y value
	// output - float value of point
	float get_value(int _x, int _y);

	float get_step();

	// return normal vector stored in 2D array
	// input - int _X value
	//		 - int _y value
	// output - vec3 value of point
	glm::vec3 get_normal(int _x, int _y);

	// generate terrain, using DS algorithm
	// input - float roughness
	void generate_terrain(float iRoughness); // vygeneruje pomoci DS algoritmu eroze povrchu

	// draw terrain
	// input ID of shadow map
	// type of shader, shadow / normal
	void draw(GLuint shadowMap, int shaderType);
	void draw(GLuint shadowMap, int shaderType, glm::vec3 pos, GLuint v, GLuint n,
			GLuint uv, GLuint e, int isize);

	// clear terrain
	void clearTerrain();

	// return Y value from X and Z coordinates
	// input - vec2
	GLfloat getInterYVal(glm::vec2 X);

	// set indexed_vertices, indexed_uvs, indexed_normals and indices
	// prepare data of terrain to drawing with buffer objects
	// simply convert 2D matrix of points and normals to 3D vectors
	// input start point and size of created square
	// return 1 = error
	int setVectors(int start_x, int start_y, int size);

	// create new buffers
	// vertex, normal, uvs and indexing
	// using vertexbuffer, uvbuffer, normalbuffer, elementbuffer
	void createBufferObject(GLuint *v, GLuint *n, GLuint *uv, GLuint *e);

	// send data from indexed_vertices, indexed_uvs,
	// indexed_normals and indices to the buffers
	// prepare buffers to draw
	void fillBufferObject(GLuint v, GLuint n, GLuint uv, GLuint e);

	// funkce pro vypocet normal terenu
	void calcNormals(int start_x, int start_y, int size);

	void setPoint(int x, int y, float value);

	void update();

	// return local maximum y value
	// x = Min
	// y = Max
	glm::vec2 getLocalMinMax(int start_x, int start_y, int size);

	GLuint getElementbuffer() const;
	void setElementbuffer(GLuint elementbuffer);
	GLuint getNormalbuffer() const;
	void setNormalbuffer(GLuint normalbuffer);
	GLuint getUvbuffer() const;
	void setUvbuffer(GLuint uvbuffer);
	GLuint getVertexbuffer() const;
	void setVertexbuffer(GLuint vertexbuffer);
	GLuint getIsize() const;
	void setIsize(GLuint isizeBuffer);

private:
	// calculate AVG of Y value in input area
	float AVGArea(int x, int y, int xSize, int ySize);
	GLuint isizeBuffer;

	GLuint tex[8]; // textures id
	GLuint shaderID, shadowShaderID;
	float gRoughness;
	int SIZE;
	float minValue, maxValue;

	// pointer to texture pool
	TexPool *textures;

	// pointer to water surface
	Water *water;

	// 2D array with y value for each vertex
	float points[MAXSIZE][MAXSIZE];

	// 2D array with normals
	glm::vec3 normals[MAXSIZE][MAXSIZE];

	// BUFFERS -----------------------------------------------------
	// buffer vectors without indexing
	std::vector<glm::vec3> buf_vertices; // vertices
	std::vector<glm::vec2> buf_uvs; // uvs coord
	std::vector<glm::vec3> buf_normals; // normals

	// all indexed
	std::vector<glm::vec3> indexed_vertices; // vertices
	std::vector<glm::vec2> indexed_uvs; // uvs coord
	std::vector<glm::vec3> indexed_normals; // normals
	std::vector<unsigned short> indices; // indexing

	// handle id of buffers
	GLuint vertexbuffer;
	GLuint uvbuffer;
	GLuint normalbuffer;
	GLuint elementbuffer;

	// -------------------------------------------------------------

	// set one vertex in vectors vertex, uvs and normal vectors
	// input - x position in the scene
	//		 - z position in the scene
	//		 - a and b position in the points arrady
	//		   there is stored y (high) value of point in scene
	void setVertex(float x, float z, int a, int b);

	float Displace(float SmallSize);
	// D-S algoritmus
	void diamondSquare(float x, float y, float width, float height, float c1,
			float c2, float c3, float c4);
	// random value
	float randInRange();

	void normalize();

	SDL_sem *Lock;
};

}
